library(here)
source(here("utils/data_processing.R"))
source(here("utils/figures.R"))

Introduction

  • Goal is to model diagnoses from each criteria based on how frequently they co-occur together within a single 10-point differential diagnosis iteration.
  • The rationale is that an ideal set of criteria will tend to result in a relatively similar set of diagnoses in each iteration and thus a small set of highly co-occurring diagnoses.
  • Conversely, a poor set of criteria will generate highly variable diagnoses in each iteration and rates of co-occurrence will be comparatively lower.

Import and process data

df_gpt3.5 <- read_model("gpt-3.5-turbo-1106", icd = FALSE)
df_gpt4.0 <- read_model("gpt-4-turbo-preview", icd = FALSE)
df_claude3_haiku_t1.0 <- read_model("claude-3-haiku-20240307_t1-0", icd = FALSE)
df_claude3_opus_t1.0 <- read_model("claude-3-opus-20240229_t1-0", icd = FALSE)
df_gemini1.0_pro_t1.0 <- read_model("gemini-1.0-pro-002_t1-0", icd = FALSE)
df_gemini1.5_pro_t1.0 <- read_model("gemini-1.5-pro-001_t1-0", icd = FALSE)
df_gpt3.5_icd <- read_model("gpt-3.5-turbo-1106", icd = TRUE)
df_gpt4.0_icd <- read_model("gpt-4-turbo-preview", icd = TRUE)
df_claude3_haiku_t1.0_icd <- read_model("claude-3-haiku-20240307_t1-0", icd = TRUE)
df_claude3_opus_t1.0_icd <- read_model("claude-3-opus-20240229_t1-0", icd = TRUE)
df_gemini1.0_pro_t1.0_icd <- read_model("gemini-1.0-pro-002_t1-0", icd = TRUE)
df_gemini1.5_pro_t1.0_icd <- read_model("gemini-1.5-pro-001_t1-0", icd = TRUE)
# Tally each pair of co-occuring diagnoses within each criteria
# Original responses
df_gpt3.5_codiag <- create_codiagnosis_df(df_gpt3.5, remove_singletons = T)
df_gpt4.0_codiag <- create_codiagnosis_df(df_gpt4.0, remove_singletons = T)
df_claude3_haiku_t1.0_codiag <- create_codiagnosis_df(df_claude3_haiku_t1.0, remove_singletons = T)
df_claude3_opus_t1.0_codiag <- create_codiagnosis_df(df_claude3_opus_t1.0, remove_singletons = T)
df_gemini1.0_pro_t1.0_codiag <- create_codiagnosis_df(df_gemini1.0_pro_t1.0, remove_singletons = T)
df_gemini1.5_pro_t1.0_codiag <- create_codiagnosis_df(df_gemini1.5_pro_t1.0, remove_singletons = T)
# ICD converted responses
df_gpt3.5_icd_codiag <- create_codiagnosis_df(df_gpt3.5_icd, remove_singletons = T)
df_gpt4.0_icd_codiag <- create_codiagnosis_df(df_gpt4.0_icd, remove_singletons = T)
df_claude3_haiku_t1.0_icd_codiag <- create_codiagnosis_df(df_claude3_haiku_t1.0_icd, remove_singletons = T)
df_claude3_opus_t1.0_icd_codiag <- create_codiagnosis_df(df_claude3_opus_t1.0_icd, remove_singletons = T)
df_gemini1.0_pro_t1.0_icd_codiag <- create_codiagnosis_df(df_gemini1.0_pro_t1.0_icd, remove_singletons = T)
df_gemini1.5_pro_t1.0_icd_codiag <- create_codiagnosis_df(df_gemini1.5_pro_t1.0_icd, remove_singletons = T)
df_gpt4.0_codiag

Graph visualization

Exploring layouts

  • To determine clearest visualization of nodes and edges
# Selecting a layout
top_n <-  200
seed <- 1234

layouts <- c("fr", "dh", "kk", "stress", "graphopt")

graph_top_gpt4 <- make_codiagnosis_graph(df_gpt4.0_codiag, n_diagnoses = top_n)

for (l in layouts){
  set.seed(seed)
  plt <- centrality_graph(graph_top_gpt4, layout = l)
  plt <- plt + ggtitle("GPT4", subtitle = sprintf("Layout %s", l))
  print(plt)
}

Individual model data

Original responses

top_n <-  100
seed <- 321
graph_layout <- "stress"

codiag_graph_wrapper <- function(data){
  set.seed(seed)
  data <- make_codiagnosis_graph(data, n_diagnoses = top_n)
  centrality_graph(data, layout = graph_layout)
}

codiag_graph_wrapper(df_gpt3.5_codiag)+ ggtitle("ChatGPT 3.5", subtitle = sprintf("Top %s", top_n))

codiag_graph_wrapper(df_gpt4.0_codiag)+ ggtitle("ChatGPT 4.0", subtitle = sprintf("Top %s", top_n))

codiag_graph_wrapper(df_claude3_haiku_t1.0_codiag)+ ggtitle("Claude 3 Haiku", subtitle = sprintf("Top %s", top_n))

codiag_graph_wrapper(df_claude3_opus_t1.0_codiag)+ ggtitle("Claude 3 Opus", subtitle = sprintf("Top %s", top_n))

codiag_graph_wrapper(df_gemini1.0_pro_t1.0_codiag)+ ggtitle("Gemini 1.0 Pro", subtitle = sprintf("Top %s", top_n))

codiag_graph_wrapper(df_gemini1.5_pro_t1.0_codiag)+ ggtitle("Gemini 1.5 Pro", subtitle = sprintf("Top %s", top_n))

ICD converted respones

codiag_graph_wrapper(df_gpt3.5_icd_codiag)+ ggtitle("ChatGPT 3.5 ICD", subtitle = sprintf("Top %s", top_n))

codiag_graph_wrapper(df_gpt4.0_icd_codiag)+ ggtitle("ChatGPT 4.0 ICD", subtitle = sprintf("Top %s", top_n))

codiag_graph_wrapper(df_claude3_haiku_t1.0_icd_codiag)+ ggtitle("Claude 3 Haiku ICD", subtitle = sprintf("Top %s", top_n))

codiag_graph_wrapper(df_claude3_opus_t1.0_icd_codiag)+ ggtitle("Claude 3 Opus ICD", subtitle = sprintf("Top %s", top_n))

codiag_graph_wrapper(df_gemini1.0_pro_t1.0_icd_codiag)+ ggtitle("Gemini 1.0 Pro ICD", subtitle = sprintf("Top %s", top_n))

codiag_graph_wrapper(df_gemini1.5_pro_t1.0_icd_codiag)+ ggtitle("Gemini 1.5 Pro ICD", subtitle = sprintf("Top %s", top_n))

Combined model data

Original responses

set.seed(seed)
multi_make_codiagnosis_graph(threshold_method = "individual", top_n = 100, layout="stress",
                             df_gpt3.5_codiag, df_gpt4.0_codiag, 
                             df_claude3_haiku_t1.0_codiag, df_claude3_opus_t1.0_codiag,
                             df_gemini1.0_pro_t1.0_codiag, df_gemini1.5_pro_t1.0_codiag)

multi_make_codiagnosis_graph(threshold_method = "average", top_n = 100, layout="stress",
                             df_gpt3.5_codiag, df_gpt4.0_codiag, 
                             df_claude3_haiku_t1.0_codiag, df_claude3_opus_t1.0_codiag,
                             df_gemini1.0_pro_t1.0_codiag, df_gemini1.5_pro_t1.0_codiag)

ICD converted responses

set.seed(seed)
multi_make_codiagnosis_graph(threshold_method = "individual", top_n = 100, layout="stress",
                             df_gpt3.5_icd_codiag, df_gpt4.0_icd_codiag, 
                             df_claude3_haiku_t1.0_icd_codiag, df_claude3_opus_t1.0_icd_codiag,
                             df_gemini1.0_pro_t1.0_icd_codiag, df_gemini1.5_pro_t1.0_icd_codiag)

multi_make_codiagnosis_graph(threshold_method = "average", top_n = 100, layout="stress",
                             df_gpt3.5_icd_codiag, df_gpt4.0_icd_codiag, 
                             df_claude3_haiku_t1.0_icd_codiag, df_claude3_opus_t1.0_icd_codiag,
                             df_gemini1.0_pro_t1.0_icd_codiag, df_gemini1.5_pro_t1.0_icd_codiag)

Evaluate edge density

  • Edge density represents the total number of edges in a graph relative to the total number of possible edges in the graph
    • When all possible edges are present, edge density = 1
    • When no edges are present, edge density = 0
  • Dense co-occurrence networks represent criteria that generate highly reproducible diagnoses. A sparse co-occurrence represents a high degree of variability

Combined model data

Original responses

multi_edge_density_plot(
  df_gpt3.5_codiag,
  df_gpt4.0_codiag,
  df_claude3_haiku_t1.0_codiag,
  df_claude3_opus_t1.0_codiag,
  df_gemini1.0_pro_t1.0_codiag,
  df_gemini1.5_pro_t1.0_codiag
)  
Warning: The `fun.y` argument of `stat_summary()` is deprecated as of ggplot2 3.3.0.
Please use the `fun` argument instead.Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
Please use `linewidth` instead.Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

ICD converted responses

plt_edge_icd <- multi_edge_density_plot(
  df_gpt3.5_icd_codiag,
  df_gpt4.0_icd_codiag,
  df_claude3_haiku_t1.0_icd_codiag,
  df_claude3_opus_t1.0_icd_codiag,
  df_gemini1.0_pro_t1.0_icd_codiag,
  df_gemini1.5_pro_t1.0_icd_codiag
)  
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.
plt_edge_icd

plt_edge_icd$data %>% summarise(mean(edge_density), .by="criteria")
extract_ggpubr_pvalues(plt_edge_icd)

Plot centrality

calculate_subgraph_centrality <- function(g, centrality_fun = "centrality_eigen"){
  data.frame(criteria = g %>% activate(edges) %>% pull(criteria) %>% unique()) %>% 
    mutate(sub_graphs = map(criteria, function(c){
      g %>% as_data_frame() %>% filter(criteria == c) %>% as_tbl_graph(directed = F) %>% 
        activate(nodes) %>% mutate(centrality = get(centrality_fun)()) %>% data.frame()
    })) %>% 
  unnest(sub_graphs) %>% 
  pivot_wider(names_from = "name", values_from = "centrality", values_fill = 0) %>% 
  column_to_rownames("criteria")
}

graph_all_gpt4 <- make_codiagnosis_graph(df_gpt4.0_codiag, n_diagnoses = top_n)
df_centr_gpt4 <- calculate_subgraph_centrality(graph_all_gpt4)
centrality_similarity <- function(data){
  data %>% 
    rownames_to_column("criteria") %>% 
    format_criteria() %>% 
    column_to_rownames("criteria") %>% 
    as.matrix() %>% 
    t() %>% 
    lsa::cosine() %>% 
    as.data.frame() %>% 
    rownames_to_column("V1") %>% 
    pivot_longer(-V1, names_to = "V2", values_to = "cosine")
}

centrality_similarity(df_centr_gpt4)
centrality_wrapper <- function(data, n_diag=NULL){
  make_codiagnosis_graph(data, n_diagnoses = n_diag) %>% 
    calculate_subgraph_centrality() %>% 
    centrality_similarity()
}

average_cosine_matrix <- listN(
  df_gpt3.5_icd_codiag,
  df_gpt4.0_icd_codiag,
  df_claude3_haiku_t1.0_icd_codiag,
  df_claude3_opus_t1.0_icd_codiag,
  df_gemini1.0_pro_t1.0_icd_codiag,
  df_gemini1.5_pro_t1.0_icd_codiag
) %>%
  enframe(name = "model", value = "data") %>%
  mutate(data = map(data, centrality_wrapper)) %>%
  unnest(data) %>%
  dplyr::summarise(cosine = mean(cosine), .by = c("V1", "V2")) %>%
  pivot_wider(names_from = "V2", values_from = "cosine") %>%
  column_to_rownames("V1") %>%
  as.matrix()

average_cosine_matrix
                   SLE - SLICC SLE - EULAR-ACR MCAS - Consortium MCAS - Alternative
SLE - SLICC          1.0000000       0.8540815         0.2276693          0.5103618
SLE - EULAR-ACR      0.8540815       1.0000000         0.2682776          0.4819197
MCAS - Consortium    0.2276693       0.2682776         1.0000000          0.5459547
MCAS - Alternative   0.5103618       0.4819197         0.5459547          1.0000000
custom_heatmap <- function(data, 
                           plot_title=NULL, 
                           legend_title=NULL, 
                           color_scale = NULL, 
                           midpoint = NULL, 
                           symmetric = T, 
                           matrix_title_size=10,
                           matrix_names_size=8,
                           legend_title_size=10,
                           legend_label_size=8,
                           dendrograms=T,
                           dendrogram_weight = unit(10, "mm"),
                           legend_orientation = "vertical",
                           legend_length=NULL,
                           grid_lines=F
                           ){
  
  # Determin midpoint of data
  scale_max <- ifelse(symmetric,max(abs(data)),max(data))
  scale_min <- ifelse(symmetric,-max(abs(data)),min(data))
  scale_mid <- scale_min + (scale_max - scale_min)/2
  
  midpoint <- ifelse(is.null(midpoint), scale_mid, midpoint)
  
  # Set default colorscales based on symmetry of data
  if (is.null(color_scale) & symmetric){color_scale <- hcl.colors(3, "Earth")}
  if (is.null(color_scale) & !symmetric){color_scale <- viridis::viridis(3)}
  color_function <-circlize::colorRamp2(c(scale_min,midpoint,scale_max), color_scale)
  
  # Legend parameters
  legend_params <- list(
      "title_gp" = grid::gpar(fontsize = legend_title_size, fontface = "bold"),
      "labels_gp" = grid::gpar(fontsize = legend_label_size),
      "direction" = legend_orientation
      )
  
  # Heatmap parameters
  heatmap_arguments <- list(
    "matrix" = data,
    "col" = color_function,
    "row_names_gp" = grid::gpar(fontsize = matrix_names_size),
    "column_names_gp" = grid::gpar(fontsize = matrix_names_size),
    "show_column_dend"=dendrograms,
    "show_row_dend"=dendrograms
  )
  
  if(!is.null(legend_title)){heatmap_arguments[["name"]] <- legend_title}
  if(grid_lines){heatmap_arguments[["rect_gp"]] <- grid::gpar(col = "black", lwd = 1)}
  if(dendrograms){
    heatmap_arguments[['column_dend_height']] <- dendrogram_weight
    heatmap_arguments[['row_dend_width']] <- dendrogram_weight
  }
  
  legend_side <- ifelse(legend_orientation=="vertical","right","bottom")
  if(legend_orientation=="vertical" &!is.null(legend_length)){
    legend_params[['legend_height']] <- legend_length}
  if(legend_orientation=="horizontal" &!is.null(legend_length)){
    legend_params[['legend_width']] <- legend_length}
  # Function call
  heatmap_arguments[["heatmap_legend_param"]] <- legend_params
  ht <- do.call(Heatmap, heatmap_arguments)
  draw(ht, heatmap_legend_side=legend_side, align_heatmap_legend = "global_center")
  
}

custom_heatmap(average_cosine_matrix, symmetric = F, legend_title = "Cosine similarity", grid_lines = T, dendrograms = F, legend_orientation = "horizontal", legend_length=unit(10, "cm"))

df_comp <- listN(
  df_gpt3.5_icd_codiag,
  df_gpt4.0_icd_codiag,
  df_claude3_haiku_t1.0_icd_codiag,
  df_claude3_opus_t1.0_icd_codiag,
  df_gemini1.0_pro_t1.0_icd_codiag,
  df_gemini1.5_pro_t1.0_icd_codiag
) %>%
  enframe(name = "model", value = "data") %>%
  mutate(data = map(data, centrality_wrapper)) %>%
  unnest(data)
cosine_similarity_compare <- function(df, pt_size=1,p_size=3){
  # Format data
  df <- df %>% 
    unite(comp, V1, V2) %>% 
    filter(comp %in% c("MCAS - Consortium_MCAS - Alternative", "SLE - EULAR-ACR_SLE - SLICC")) %>% 
    mutate(comp = gsub("_", "\nvs. ", comp)) %>%  
    format_models() 
  
  # Plot data
  df %>% 
    ggplot(aes(x = comp, y = cosine, color = model))+
    geom_point(size=pt_size, position = position_dodge(width = 0.75))+
    theme_bw() +
    ggpubr::stat_compare_means(aes(group = comp), method = "wilcox.test", label = "p", vjust = 0.75, show.legend = F, size = p_size)+
    theme(axis.text.x = element_text(angle =90, hjust =1)) +
    labs(x=NULL, y="Cosine similarity") +
    scale_color_brewer(palette = "Dark2") +
    labs(color = "")
}
cosine_similarity_compare(df_comp)

individual_cosine_matrix <- combine_data_frames(
  df_gpt3.5_icd_codiag,
  df_gpt4.0_icd_codiag,
  df_claude3_haiku_t1.0_icd_codiag,
  df_claude3_opus_t1.0_icd_codiag,
  df_gemini1.0_pro_t1.0_icd_codiag,
  df_gemini1.5_pro_t1.0_icd_codiag,
  additional_function = function(x){
    x %>% 
      make_codiagnosis_graph() %>% 
      calculate_subgraph_centrality() %>% 
      rownames_to_column("criteria") %>% 
      pivot_longer(-criteria, names_to="diagnosis", values_to="centrality")
  }
) %>% 
  unite(criteria, original_df, criteria, sep = "-") %>% 
  pivot_wider(names_from = "diagnosis", values_from="centrality", values_fill=0) %>% 
  column_to_rownames("criteria") %>% 
  as.matrix() %>% 
    t() %>% 
    lsa::cosine() %>% 
    as.data.frame() %>% 
    rownames_to_column("V1") %>% 
    pivot_longer(-V1, names_to = "V2", values_to = "cosine") %>% 
  pivot_wider(names_from = "V2", values_from = "cosine") %>%
  column_to_rownames("V1") %>%
  as.matrix()

individual_cosine_matrix[1:5, 1:5]
                                      df_gpt3.5_icd_codiag-slicc_sle df_gpt3.5_icd_codiag-eular_acr_sle df_gpt3.5_icd_codiag-mcas_consortium df_gpt3.5_icd_codiag-mcas_alternative df_gpt4.0_icd_codiag-slicc_sle
df_gpt3.5_icd_codiag-slicc_sle                             1.0000000                          0.8647922                            0.2136086                             0.4720033                      0.8522184
df_gpt3.5_icd_codiag-eular_acr_sle                         0.8647922                          1.0000000                            0.2643814                             0.4613926                      0.7324445
df_gpt3.5_icd_codiag-mcas_consortium                       0.2136086                          0.2643814                            1.0000000                             0.5519625                      0.1663721
df_gpt3.5_icd_codiag-mcas_alternative                      0.4720033                          0.4613926                            0.5519625                             1.0000000                      0.3913603
df_gpt4.0_icd_codiag-slicc_sle                             0.8522184                          0.7324445                            0.1663721                             0.3913603                      1.0000000
alt_heatmap <- model_criteria_heatmap(individual_cosine_matrix, 
                color_scale = viridis::viridis(3), 
                title = " ", 
                metric = "Cosine\nsimilarity", 
                symmetric = F,
                font_size = 8)  
  

alt_heatmap

plt_alt <- cowplot::plot_grid(grid::grid.grabExpr(ComplexHeatmap::draw(alt_heatmap)))
plt_alt

ggsave(here("figures/4_alt_heatmap.pdf"), plot=plt_alt, height = 4, width = 6)

Final plot

Version 1

# n_diagnoses_bar <- 10
# n_diagnoses_abundance <- 50
# n_diagnoses_cumulative <- 50

title_size <- 9
label_size <- 6
legend_x_pad <- 0
legend_y_pad <- 0

apply_text_formatting <- list(
  theme(
    axis.text = element_text(size = label_size),
    axis.title = element_text(size = title_size),
    legend.text = element_text(size = label_size, margin=margin(0,0,0,r=1)),
    strip.text = element_text(size = label_size + 1),
    legend.key.height = unit(0.4, 'cm'),
    legend.key.width = unit(0.4, 'cm'),
    # legend.key = element_rect(size =  margin(0,0,0,0)),
    legend.box.background = element_rect(color = "black", size = 1),
    legend.margin = margin(
      t = legend_y_pad,
      r = legend_x_pad+2,
      b = legend_y_pad,
      l = legend_x_pad 
    ),
    legend.key.spacing.y = unit(-1.5, "pt"),
    legend.box.spacing = unit(1.0,"pt"),
    axis.text.x=element_text(angle=45,hjust=1)
  )
)


strip_margin <- 1
strip_formatting <- list(theme(
  strip.text.x = element_text(margin = margin(t = strip_margin, r = strip_margin, b = strip_margin, l = strip_margin)), 
  strip.text.y = element_text(margin = margin(t = strip_margin, r = strip_margin, b = strip_margin, l = strip_margin))
  # strip.background = element_rect(margin = margin(t = strip_margin, r = strip_margin, b = strip_margin, l = strip_margin))
))

set.seed(1234)
plt_graph_icd <-
  multi_make_codiagnosis_graph(
    threshold_method = "average",
    top_n = 100,
    layout = "stress",
    df_gpt3.5_codiag,
    df_gpt4.0_codiag,
    df_claude3_haiku_t1.0_codiag,
    df_claude3_opus_t1.0_codiag,
    df_gemini1.0_pro_t1.0_codiag,
    df_gemini1.5_pro_t1.0_codiag,
    point_size = 1.25,
    border_size = 0.25,
    edge_width = 0.5,
    edge_alpha = 0.5,
    label_text_size = 9,
    tick_text_size = 6,
    highlight_stroke_multiplier = 3,
    legend_height = unit(25, "pt"),
    legend_width = unit(10, "pt")
  ) 

plt_edge_icd <- multi_edge_density_plot(
  df_gpt3.5_icd_codiag,
  df_gpt4.0_icd_codiag,
  df_claude3_haiku_t1.0_icd_codiag,
  df_claude3_opus_t1.0_icd_codiag,
  df_gemini1.0_pro_t1.0_icd_codiag,
  df_gemini1.5_pro_t1.0_icd_codiag
)  +
  apply_text_formatting +
  theme(legend.position = "bottom", legend.direction = "horizontal") +
  guides(color = guide_legend(override.aes = list(size = 1), ncol = 2))+
  scale_y_continuous(expand = expansion(mult = c(0.1,0.1)))
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.
plt_heatmap_icd <-
  custom_heatmap(
    average_cosine_matrix,
    symmetric = F,
    legend_title = "Cosine similarity",
    grid_lines = T,
    dendrograms = T,
    legend_orientation = "horizontal",
    legend_length = unit(2, "cm"),
    matrix_names_size = 6, 
    legend_title_size = 7.5,
    legend_label_size = 6,
    dendrogram_weight = unit(2, "mm")
  )



plt_fig <- cowplot::plot_grid(
  NULL, 
  cowplot::plot_grid(
    NULL, plt_graph_icd, nrow=1, rel_widths=c(0.05,0.9)
  ),
  NULL,
  cowplot::plot_grid(
    cowplot::plot_grid(NULL, plt_edge_icd ,rel_heights = c(0.1, 1), ncol = 1),
    NULL,
    cowplot::plot_grid(
      NULL,
      grid::grid.grabExpr(ComplexHeatmap::draw(plt_heatmap_icd, heatmap_legend_side = "bottom")),
      NULL,
      ncol = 1,
      rel_heights = c(0.1, 1, 0.1)
    ),
    nrow = 1,
    rel_widths = c(0.5, 0.1, 0.6),
    labels = c("B","","C"),
    axis = 'h', align = 'bt'
  ),

  ncol = 1,
  rel_heights = c(0.05, 0.95, 0.01, 1),
  labels = c("A")
)

plt_fig 

Version 2

ggsave(here("figures/4_Network_analysis.pdf"), plot=plt_fig, height = 5.5, width = 3.5)
LS0tCnRpdGxlOiAiQ28tZGlhZ25vc2lzIG5ldHdvcmsgYW5hbHlzaXMiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKYGBge3IsIG1lc3NhZ2UgPSBGfQpsaWJyYXJ5KGhlcmUpCnNvdXJjZShoZXJlKCJ1dGlscy9kYXRhX3Byb2Nlc3NpbmcuUiIpKQpzb3VyY2UoaGVyZSgidXRpbHMvZmlndXJlcy5SIikpCmBgYAoKIyBJbnRyb2R1Y3Rpb24KCi0gR29hbCBpcyB0byBtb2RlbCBkaWFnbm9zZXMgZnJvbSBlYWNoIGNyaXRlcmlhIGJhc2VkIG9uIGhvdyBmcmVxdWVudGx5IHRoZXkgY28tb2NjdXIgdG9nZXRoZXIgd2l0aGluIGEgc2luZ2xlIDEwLXBvaW50IGRpZmZlcmVudGlhbCBkaWFnbm9zaXMgaXRlcmF0aW9uLgotIFRoZSByYXRpb25hbGUgaXMgdGhhdCBhbiBpZGVhbCBzZXQgb2YgY3JpdGVyaWEgd2lsbCB0ZW5kIHRvIHJlc3VsdCBpbiBhIHJlbGF0aXZlbHkgc2ltaWxhciBzZXQgb2YgZGlhZ25vc2VzIGluIGVhY2ggaXRlcmF0aW9uIGFuZCB0aHVzIGEgc21hbGwgc2V0IG9mIGhpZ2hseSBjby1vY2N1cnJpbmcgZGlhZ25vc2VzLgotIENvbnZlcnNlbHksIGEgcG9vciBzZXQgb2YgY3JpdGVyaWEgd2lsbCBnZW5lcmF0ZSBoaWdobHkgdmFyaWFibGUgZGlhZ25vc2VzIGluIGVhY2ggaXRlcmF0aW9uIGFuZCByYXRlcyBvZiBjby1vY2N1cnJlbmNlIHdpbGwgYmUgY29tcGFyYXRpdmVseSBsb3dlci4KCiMgSW1wb3J0IGFuZCBwcm9jZXNzIGRhdGEKCmBgYHtyLCBtZXNzYWdlID0gRn0KZGZfZ3B0My41IDwtIHJlYWRfbW9kZWwoImdwdC0zLjUtdHVyYm8tMTEwNiIsIGljZCA9IEZBTFNFKQpkZl9ncHQ0LjAgPC0gcmVhZF9tb2RlbCgiZ3B0LTQtdHVyYm8tcHJldmlldyIsIGljZCA9IEZBTFNFKQpkZl9jbGF1ZGUzX2hhaWt1X3QxLjAgPC0gcmVhZF9tb2RlbCgiY2xhdWRlLTMtaGFpa3UtMjAyNDAzMDdfdDEtMCIsIGljZCA9IEZBTFNFKQpkZl9jbGF1ZGUzX29wdXNfdDEuMCA8LSByZWFkX21vZGVsKCJjbGF1ZGUtMy1vcHVzLTIwMjQwMjI5X3QxLTAiLCBpY2QgPSBGQUxTRSkKZGZfZ2VtaW5pMS4wX3Byb190MS4wIDwtIHJlYWRfbW9kZWwoImdlbWluaS0xLjAtcHJvLTAwMl90MS0wIiwgaWNkID0gRkFMU0UpCmRmX2dlbWluaTEuNV9wcm9fdDEuMCA8LSByZWFkX21vZGVsKCJnZW1pbmktMS41LXByby0wMDFfdDEtMCIsIGljZCA9IEZBTFNFKQpgYGAKCmBgYHtyLCBtZXNzYWdlID0gRn0KZGZfZ3B0My41X2ljZCA8LSByZWFkX21vZGVsKCJncHQtMy41LXR1cmJvLTExMDYiLCBpY2QgPSBUUlVFKQpkZl9ncHQ0LjBfaWNkIDwtIHJlYWRfbW9kZWwoImdwdC00LXR1cmJvLXByZXZpZXciLCBpY2QgPSBUUlVFKQpkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfaWNkIDwtIHJlYWRfbW9kZWwoImNsYXVkZS0zLWhhaWt1LTIwMjQwMzA3X3QxLTAiLCBpY2QgPSBUUlVFKQpkZl9jbGF1ZGUzX29wdXNfdDEuMF9pY2QgPC0gcmVhZF9tb2RlbCgiY2xhdWRlLTMtb3B1cy0yMDI0MDIyOV90MS0wIiwgaWNkID0gVFJVRSkKZGZfZ2VtaW5pMS4wX3Byb190MS4wX2ljZCA8LSByZWFkX21vZGVsKCJnZW1pbmktMS4wLXByby0wMDJfdDEtMCIsIGljZCA9IFRSVUUpCmRmX2dlbWluaTEuNV9wcm9fdDEuMF9pY2QgPC0gcmVhZF9tb2RlbCgiZ2VtaW5pLTEuNS1wcm8tMDAxX3QxLTAiLCBpY2QgPSBUUlVFKQpgYGAKCmBgYHtyfQojIFRhbGx5IGVhY2ggcGFpciBvZiBjby1vY2N1cmluZyBkaWFnbm9zZXMgd2l0aGluIGVhY2ggY3JpdGVyaWEKIyBPcmlnaW5hbCByZXNwb25zZXMKZGZfZ3B0My41X2NvZGlhZyA8LSBjcmVhdGVfY29kaWFnbm9zaXNfZGYoZGZfZ3B0My41LCByZW1vdmVfc2luZ2xldG9ucyA9IFQpCmRmX2dwdDQuMF9jb2RpYWcgPC0gY3JlYXRlX2NvZGlhZ25vc2lzX2RmKGRmX2dwdDQuMCwgcmVtb3ZlX3NpbmdsZXRvbnMgPSBUKQpkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfY29kaWFnIDwtIGNyZWF0ZV9jb2RpYWdub3Npc19kZihkZl9jbGF1ZGUzX2hhaWt1X3QxLjAsIHJlbW92ZV9zaW5nbGV0b25zID0gVCkKZGZfY2xhdWRlM19vcHVzX3QxLjBfY29kaWFnIDwtIGNyZWF0ZV9jb2RpYWdub3Npc19kZihkZl9jbGF1ZGUzX29wdXNfdDEuMCwgcmVtb3ZlX3NpbmdsZXRvbnMgPSBUKQpkZl9nZW1pbmkxLjBfcHJvX3QxLjBfY29kaWFnIDwtIGNyZWF0ZV9jb2RpYWdub3Npc19kZihkZl9nZW1pbmkxLjBfcHJvX3QxLjAsIHJlbW92ZV9zaW5nbGV0b25zID0gVCkKZGZfZ2VtaW5pMS41X3Byb190MS4wX2NvZGlhZyA8LSBjcmVhdGVfY29kaWFnbm9zaXNfZGYoZGZfZ2VtaW5pMS41X3Byb190MS4wLCByZW1vdmVfc2luZ2xldG9ucyA9IFQpCmBgYAoKCmBgYHtyfQojIElDRCBjb252ZXJ0ZWQgcmVzcG9uc2VzCmRmX2dwdDMuNV9pY2RfY29kaWFnIDwtIGNyZWF0ZV9jb2RpYWdub3Npc19kZihkZl9ncHQzLjVfaWNkLCByZW1vdmVfc2luZ2xldG9ucyA9IFQpCmRmX2dwdDQuMF9pY2RfY29kaWFnIDwtIGNyZWF0ZV9jb2RpYWdub3Npc19kZihkZl9ncHQ0LjBfaWNkLCByZW1vdmVfc2luZ2xldG9ucyA9IFQpCmRmX2NsYXVkZTNfaGFpa3VfdDEuMF9pY2RfY29kaWFnIDwtIGNyZWF0ZV9jb2RpYWdub3Npc19kZihkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfaWNkLCByZW1vdmVfc2luZ2xldG9ucyA9IFQpCmRmX2NsYXVkZTNfb3B1c190MS4wX2ljZF9jb2RpYWcgPC0gY3JlYXRlX2NvZGlhZ25vc2lzX2RmKGRmX2NsYXVkZTNfb3B1c190MS4wX2ljZCwgcmVtb3ZlX3NpbmdsZXRvbnMgPSBUKQpkZl9nZW1pbmkxLjBfcHJvX3QxLjBfaWNkX2NvZGlhZyA8LSBjcmVhdGVfY29kaWFnbm9zaXNfZGYoZGZfZ2VtaW5pMS4wX3Byb190MS4wX2ljZCwgcmVtb3ZlX3NpbmdsZXRvbnMgPSBUKQpkZl9nZW1pbmkxLjVfcHJvX3QxLjBfaWNkX2NvZGlhZyA8LSBjcmVhdGVfY29kaWFnbm9zaXNfZGYoZGZfZ2VtaW5pMS41X3Byb190MS4wX2ljZCwgcmVtb3ZlX3NpbmdsZXRvbnMgPSBUKQpgYGAKCgpgYGB7cn0KZGZfZ3B0NC4wX2NvZGlhZwpgYGAKCiMgR3JhcGggdmlzdWFsaXphdGlvbgoKIyMgRXhwbG9yaW5nIGxheW91dHMgCgotIFRvIGRldGVybWluZSBjbGVhcmVzdCB2aXN1YWxpemF0aW9uIG9mIG5vZGVzIGFuZCBlZGdlcwoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTYsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQojIFNlbGVjdGluZyBhIGxheW91dAp0b3BfbiA8LSAgMjAwCnNlZWQgPC0gMTIzNAoKbGF5b3V0cyA8LSBjKCJmciIsICJkaCIsICJrayIsICJzdHJlc3MiLCAiZ3JhcGhvcHQiKQoKZ3JhcGhfdG9wX2dwdDQgPC0gbWFrZV9jb2RpYWdub3Npc19ncmFwaChkZl9ncHQ0LjBfY29kaWFnLCBuX2RpYWdub3NlcyA9IHRvcF9uKQoKZm9yIChsIGluIGxheW91dHMpewogIHNldC5zZWVkKHNlZWQpCiAgcGx0IDwtIGNlbnRyYWxpdHlfZ3JhcGgoZ3JhcGhfdG9wX2dwdDQsIGxheW91dCA9IGwpCiAgcGx0IDwtIHBsdCArIGdndGl0bGUoIkdQVDQiLCBzdWJ0aXRsZSA9IHNwcmludGYoIkxheW91dCAlcyIsIGwpKQogIHByaW50KHBsdCkKfQoKYGBgCiMjIEluZGl2aWR1YWwgbW9kZWwgZGF0YQoKIyMjIE9yaWdpbmFsIHJlc3BvbnNlcwoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTYsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQp0b3BfbiA8LSAgMTAwCnNlZWQgPC0gMzIxCmdyYXBoX2xheW91dCA8LSAic3RyZXNzIgoKY29kaWFnX2dyYXBoX3dyYXBwZXIgPC0gZnVuY3Rpb24oZGF0YSl7CiAgc2V0LnNlZWQoc2VlZCkKICBkYXRhIDwtIG1ha2VfY29kaWFnbm9zaXNfZ3JhcGgoZGF0YSwgbl9kaWFnbm9zZXMgPSB0b3BfbikKICBjZW50cmFsaXR5X2dyYXBoKGRhdGEsIGxheW91dCA9IGdyYXBoX2xheW91dCkKfQoKY29kaWFnX2dyYXBoX3dyYXBwZXIoZGZfZ3B0My41X2NvZGlhZykrIGdndGl0bGUoIkNoYXRHUFQgMy41Iiwgc3VidGl0bGUgPSBzcHJpbnRmKCJUb3AgJXMiLCB0b3BfbikpCmNvZGlhZ19ncmFwaF93cmFwcGVyKGRmX2dwdDQuMF9jb2RpYWcpKyBnZ3RpdGxlKCJDaGF0R1BUIDQuMCIsIHN1YnRpdGxlID0gc3ByaW50ZigiVG9wICVzIiwgdG9wX24pKQpjb2RpYWdfZ3JhcGhfd3JhcHBlcihkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfY29kaWFnKSsgZ2d0aXRsZSgiQ2xhdWRlIDMgSGFpa3UiLCBzdWJ0aXRsZSA9IHNwcmludGYoIlRvcCAlcyIsIHRvcF9uKSkKY29kaWFnX2dyYXBoX3dyYXBwZXIoZGZfY2xhdWRlM19vcHVzX3QxLjBfY29kaWFnKSsgZ2d0aXRsZSgiQ2xhdWRlIDMgT3B1cyIsIHN1YnRpdGxlID0gc3ByaW50ZigiVG9wICVzIiwgdG9wX24pKQpjb2RpYWdfZ3JhcGhfd3JhcHBlcihkZl9nZW1pbmkxLjBfcHJvX3QxLjBfY29kaWFnKSsgZ2d0aXRsZSgiR2VtaW5pIDEuMCBQcm8iLCBzdWJ0aXRsZSA9IHNwcmludGYoIlRvcCAlcyIsIHRvcF9uKSkKY29kaWFnX2dyYXBoX3dyYXBwZXIoZGZfZ2VtaW5pMS41X3Byb190MS4wX2NvZGlhZykrIGdndGl0bGUoIkdlbWluaSAxLjUgUHJvIiwgc3VidGl0bGUgPSBzcHJpbnRmKCJUb3AgJXMiLCB0b3BfbikpCmBgYAojIyMgSUNEIGNvbnZlcnRlZCByZXNwb25lcwoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTYsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQpjb2RpYWdfZ3JhcGhfd3JhcHBlcihkZl9ncHQzLjVfaWNkX2NvZGlhZykrIGdndGl0bGUoIkNoYXRHUFQgMy41IElDRCIsIHN1YnRpdGxlID0gc3ByaW50ZigiVG9wICVzIiwgdG9wX24pKQpjb2RpYWdfZ3JhcGhfd3JhcHBlcihkZl9ncHQ0LjBfaWNkX2NvZGlhZykrIGdndGl0bGUoIkNoYXRHUFQgNC4wIElDRCIsIHN1YnRpdGxlID0gc3ByaW50ZigiVG9wICVzIiwgdG9wX24pKQpjb2RpYWdfZ3JhcGhfd3JhcHBlcihkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfaWNkX2NvZGlhZykrIGdndGl0bGUoIkNsYXVkZSAzIEhhaWt1IElDRCIsIHN1YnRpdGxlID0gc3ByaW50ZigiVG9wICVzIiwgdG9wX24pKQpjb2RpYWdfZ3JhcGhfd3JhcHBlcihkZl9jbGF1ZGUzX29wdXNfdDEuMF9pY2RfY29kaWFnKSsgZ2d0aXRsZSgiQ2xhdWRlIDMgT3B1cyBJQ0QiLCBzdWJ0aXRsZSA9IHNwcmludGYoIlRvcCAlcyIsIHRvcF9uKSkKY29kaWFnX2dyYXBoX3dyYXBwZXIoZGZfZ2VtaW5pMS4wX3Byb190MS4wX2ljZF9jb2RpYWcpKyBnZ3RpdGxlKCJHZW1pbmkgMS4wIFBybyBJQ0QiLCBzdWJ0aXRsZSA9IHNwcmludGYoIlRvcCAlcyIsIHRvcF9uKSkKY29kaWFnX2dyYXBoX3dyYXBwZXIoZGZfZ2VtaW5pMS41X3Byb190MS4wX2ljZF9jb2RpYWcpKyBnZ3RpdGxlKCJHZW1pbmkgMS41IFBybyBJQ0QiLCBzdWJ0aXRsZSA9IHNwcmludGYoIlRvcCAlcyIsIHRvcF9uKSkKYGBgCgojIyBDb21iaW5lZCBtb2RlbCBkYXRhCgojIyMgT3JpZ2luYWwgcmVzcG9uc2VzCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Niwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CnNldC5zZWVkKHNlZWQpCm11bHRpX21ha2VfY29kaWFnbm9zaXNfZ3JhcGgodGhyZXNob2xkX21ldGhvZCA9ICJpbmRpdmlkdWFsIiwgdG9wX24gPSAxMDAsIGxheW91dD0ic3RyZXNzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZl9ncHQzLjVfY29kaWFnLCBkZl9ncHQ0LjBfY29kaWFnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfY29kaWFnLCBkZl9jbGF1ZGUzX29wdXNfdDEuMF9jb2RpYWcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGZfZ2VtaW5pMS4wX3Byb190MS4wX2NvZGlhZywgZGZfZ2VtaW5pMS41X3Byb190MS4wX2NvZGlhZykKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Niwgd2FybmluZz1GLCBtZXNzYWdlPUZ9Cm11bHRpX21ha2VfY29kaWFnbm9zaXNfZ3JhcGgodGhyZXNob2xkX21ldGhvZCA9ICJhdmVyYWdlIiwgdG9wX24gPSAxMDAsIGxheW91dD0ic3RyZXNzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZl9ncHQzLjVfY29kaWFnLCBkZl9ncHQ0LjBfY29kaWFnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfY29kaWFnLCBkZl9jbGF1ZGUzX29wdXNfdDEuMF9jb2RpYWcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGZfZ2VtaW5pMS4wX3Byb190MS4wX2NvZGlhZywgZGZfZ2VtaW5pMS41X3Byb190MS4wX2NvZGlhZykKYGBgCiMjIyBJQ0QgY29udmVydGVkIHJlc3BvbnNlcwoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTYsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQpzZXQuc2VlZChzZWVkKQptdWx0aV9tYWtlX2NvZGlhZ25vc2lzX2dyYXBoKHRocmVzaG9sZF9tZXRob2QgPSAiaW5kaXZpZHVhbCIsIHRvcF9uID0gMTAwLCBsYXlvdXQ9InN0cmVzcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGZfZ3B0My41X2ljZF9jb2RpYWcsIGRmX2dwdDQuMF9pY2RfY29kaWFnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfaWNkX2NvZGlhZywgZGZfY2xhdWRlM19vcHVzX3QxLjBfaWNkX2NvZGlhZywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZl9nZW1pbmkxLjBfcHJvX3QxLjBfaWNkX2NvZGlhZywgZGZfZ2VtaW5pMS41X3Byb190MS4wX2ljZF9jb2RpYWcpCmBgYAoKCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02LCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KbXVsdGlfbWFrZV9jb2RpYWdub3Npc19ncmFwaCh0aHJlc2hvbGRfbWV0aG9kID0gImF2ZXJhZ2UiLCB0b3BfbiA9IDEwMCwgbGF5b3V0PSJzdHJlc3MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmX2dwdDMuNV9pY2RfY29kaWFnLCBkZl9ncHQ0LjBfaWNkX2NvZGlhZywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGZfY2xhdWRlM19oYWlrdV90MS4wX2ljZF9jb2RpYWcsIGRmX2NsYXVkZTNfb3B1c190MS4wX2ljZF9jb2RpYWcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGZfZ2VtaW5pMS4wX3Byb190MS4wX2ljZF9jb2RpYWcsIGRmX2dlbWluaTEuNV9wcm9fdDEuMF9pY2RfY29kaWFnKQpgYGAKCiMgRXZhbHVhdGUgZWRnZSBkZW5zaXR5CgotIEVkZ2UgZGVuc2l0eSByZXByZXNlbnRzIHRoZSB0b3RhbCBudW1iZXIgb2YgZWRnZXMgaW4gYSBncmFwaCByZWxhdGl2ZSB0byB0aGUgdG90YWwgbnVtYmVyIG9mICoqcG9zc2libGUqKiBlZGdlcyBpbiB0aGUgZ3JhcGgKICAtIFdoZW4gYWxsIHBvc3NpYmxlIGVkZ2VzIGFyZSBwcmVzZW50LCBlZGdlIGRlbnNpdHkgPSAxCiAgLSBXaGVuIG5vIGVkZ2VzIGFyZSBwcmVzZW50LCBlZGdlIGRlbnNpdHkgPSAwCi0gRGVuc2UgY28tb2NjdXJyZW5jZSBuZXR3b3JrcyByZXByZXNlbnQgY3JpdGVyaWEgdGhhdCBnZW5lcmF0ZSBoaWdobHkgcmVwcm9kdWNpYmxlIGRpYWdub3Nlcy4gQSBzcGFyc2UgY28tb2NjdXJyZW5jZSByZXByZXNlbnRzIGEgaGlnaCBkZWdyZWUgb2YgdmFyaWFiaWxpdHkgCgojIyBDb21iaW5lZCBtb2RlbCBkYXRhCgojIyMgT3JpZ2luYWwgcmVzcG9uc2VzCgpgYGB7ciwgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9My41fQptdWx0aV9lZGdlX2RlbnNpdHlfcGxvdCgKICBkZl9ncHQzLjVfY29kaWFnLAogIGRmX2dwdDQuMF9jb2RpYWcsCiAgZGZfY2xhdWRlM19oYWlrdV90MS4wX2NvZGlhZywKICBkZl9jbGF1ZGUzX29wdXNfdDEuMF9jb2RpYWcsCiAgZGZfZ2VtaW5pMS4wX3Byb190MS4wX2NvZGlhZywKICBkZl9nZW1pbmkxLjVfcHJvX3QxLjBfY29kaWFnCikgIAoKYGBgCgojIyMgSUNEIGNvbnZlcnRlZCByZXNwb25zZXMKCmBgYHtyLCBmaWcud2lkdGg9NCwgZmlnLmhlaWdodD0zLjV9CnBsdF9lZGdlX2ljZCA8LSBtdWx0aV9lZGdlX2RlbnNpdHlfcGxvdCgKICBkZl9ncHQzLjVfaWNkX2NvZGlhZywKICBkZl9ncHQ0LjBfaWNkX2NvZGlhZywKICBkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfaWNkX2NvZGlhZywKICBkZl9jbGF1ZGUzX29wdXNfdDEuMF9pY2RfY29kaWFnLAogIGRmX2dlbWluaTEuMF9wcm9fdDEuMF9pY2RfY29kaWFnLAogIGRmX2dlbWluaTEuNV9wcm9fdDEuMF9pY2RfY29kaWFnCikgIApwbHRfZWRnZV9pY2QKcGx0X2VkZ2VfaWNkJGRhdGEgJT4lIHN1bW1hcmlzZShtZWFuKGVkZ2VfZGVuc2l0eSksIC5ieT0iY3JpdGVyaWEiKQpleHRyYWN0X2dncHVicl9wdmFsdWVzKHBsdF9lZGdlX2ljZCkKYGBgCgoKCiMgUGxvdCBjZW50cmFsaXR5CmBgYHtyfQpjYWxjdWxhdGVfc3ViZ3JhcGhfY2VudHJhbGl0eSA8LSBmdW5jdGlvbihnLCBjZW50cmFsaXR5X2Z1biA9ICJjZW50cmFsaXR5X2VpZ2VuIil7CiAgZGF0YS5mcmFtZShjcml0ZXJpYSA9IGcgJT4lIGFjdGl2YXRlKGVkZ2VzKSAlPiUgcHVsbChjcml0ZXJpYSkgJT4lIHVuaXF1ZSgpKSAlPiUgCiAgICBtdXRhdGUoc3ViX2dyYXBocyA9IG1hcChjcml0ZXJpYSwgZnVuY3Rpb24oYyl7CiAgICAgIGcgJT4lIGFzX2RhdGFfZnJhbWUoKSAlPiUgZmlsdGVyKGNyaXRlcmlhID09IGMpICU+JSBhc190YmxfZ3JhcGgoZGlyZWN0ZWQgPSBGKSAlPiUgCiAgICAgICAgYWN0aXZhdGUobm9kZXMpICU+JSBtdXRhdGUoY2VudHJhbGl0eSA9IGdldChjZW50cmFsaXR5X2Z1bikoKSkgJT4lIGRhdGEuZnJhbWUoKQogICAgfSkpICU+JSAKICB1bm5lc3Qoc3ViX2dyYXBocykgJT4lIAogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSAibmFtZSIsIHZhbHVlc19mcm9tID0gImNlbnRyYWxpdHkiLCB2YWx1ZXNfZmlsbCA9IDApICU+JSAKICBjb2x1bW5fdG9fcm93bmFtZXMoImNyaXRlcmlhIikKfQoKZ3JhcGhfYWxsX2dwdDQgPC0gbWFrZV9jb2RpYWdub3Npc19ncmFwaChkZl9ncHQ0LjBfY29kaWFnLCBuX2RpYWdub3NlcyA9IHRvcF9uKQpkZl9jZW50cl9ncHQ0IDwtIGNhbGN1bGF0ZV9zdWJncmFwaF9jZW50cmFsaXR5KGdyYXBoX2FsbF9ncHQ0KQpgYGAKCmBgYHtyfQpjZW50cmFsaXR5X3NpbWlsYXJpdHkgPC0gZnVuY3Rpb24oZGF0YSl7CiAgZGF0YSAlPiUgCiAgICByb3duYW1lc190b19jb2x1bW4oImNyaXRlcmlhIikgJT4lIAogICAgZm9ybWF0X2NyaXRlcmlhKCkgJT4lIAogICAgY29sdW1uX3RvX3Jvd25hbWVzKCJjcml0ZXJpYSIpICU+JSAKICAgIGFzLm1hdHJpeCgpICU+JSAKICAgIHQoKSAlPiUgCiAgICBsc2E6OmNvc2luZSgpICU+JSAKICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgICByb3duYW1lc190b19jb2x1bW4oIlYxIikgJT4lIAogICAgcGl2b3RfbG9uZ2VyKC1WMSwgbmFtZXNfdG8gPSAiVjIiLCB2YWx1ZXNfdG8gPSAiY29zaW5lIikKfQoKY2VudHJhbGl0eV9zaW1pbGFyaXR5KGRmX2NlbnRyX2dwdDQpCmBgYApgYGB7cn0KY2VudHJhbGl0eV93cmFwcGVyIDwtIGZ1bmN0aW9uKGRhdGEsIG5fZGlhZz1OVUxMKXsKICBtYWtlX2NvZGlhZ25vc2lzX2dyYXBoKGRhdGEsIG5fZGlhZ25vc2VzID0gbl9kaWFnKSAlPiUgCiAgICBjYWxjdWxhdGVfc3ViZ3JhcGhfY2VudHJhbGl0eSgpICU+JSAKICAgIGNlbnRyYWxpdHlfc2ltaWxhcml0eSgpCn0KCmF2ZXJhZ2VfY29zaW5lX21hdHJpeCA8LSBsaXN0TigKICBkZl9ncHQzLjVfaWNkX2NvZGlhZywKICBkZl9ncHQ0LjBfaWNkX2NvZGlhZywKICBkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfaWNkX2NvZGlhZywKICBkZl9jbGF1ZGUzX29wdXNfdDEuMF9pY2RfY29kaWFnLAogIGRmX2dlbWluaTEuMF9wcm9fdDEuMF9pY2RfY29kaWFnLAogIGRmX2dlbWluaTEuNV9wcm9fdDEuMF9pY2RfY29kaWFnCikgJT4lCiAgZW5mcmFtZShuYW1lID0gIm1vZGVsIiwgdmFsdWUgPSAiZGF0YSIpICU+JQogIG11dGF0ZShkYXRhID0gbWFwKGRhdGEsIGNlbnRyYWxpdHlfd3JhcHBlcikpICU+JQogIHVubmVzdChkYXRhKSAlPiUKICBkcGx5cjo6c3VtbWFyaXNlKGNvc2luZSA9IG1lYW4oY29zaW5lKSwgLmJ5ID0gYygiVjEiLCAiVjIiKSkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9ICJWMiIsIHZhbHVlc19mcm9tID0gImNvc2luZSIpICU+JQogIGNvbHVtbl90b19yb3duYW1lcygiVjEiKSAlPiUKICBhcy5tYXRyaXgoKQoKYXZlcmFnZV9jb3NpbmVfbWF0cml4CgoKYGBgCgoKCmBgYHtyLCBmaWcud2lkdGg9NS41LCBmaWcuaGVpZ2h0PTMuNX0KY3VzdG9tX2hlYXRtYXAgPC0gZnVuY3Rpb24oZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3RfdGl0bGU9TlVMTCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlZ2VuZF90aXRsZT1OVUxMLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3Jfc2NhbGUgPSBOVUxMLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbWlkcG9pbnQgPSBOVUxMLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ltbWV0cmljID0gVCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hdHJpeF90aXRsZV9zaXplPTEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICBtYXRyaXhfbmFtZXNfc2l6ZT04LAogICAgICAgICAgICAgICAgICAgICAgICAgICBsZWdlbmRfdGl0bGVfc2l6ZT0xMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVnZW5kX2xhYmVsX3NpemU9OCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVuZHJvZ3JhbXM9VCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVuZHJvZ3JhbV93ZWlnaHQgPSB1bml0KDEwLCAibW0iKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVnZW5kX29yaWVudGF0aW9uID0gInZlcnRpY2FsIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVnZW5kX2xlbmd0aD1OVUxMLAogICAgICAgICAgICAgICAgICAgICAgICAgICBncmlkX2xpbmVzPUYKICAgICAgICAgICAgICAgICAgICAgICAgICAgKXsKICAKICAjIERldGVybWluIG1pZHBvaW50IG9mIGRhdGEKICBzY2FsZV9tYXggPC0gaWZlbHNlKHN5bW1ldHJpYyxtYXgoYWJzKGRhdGEpKSxtYXgoZGF0YSkpCiAgc2NhbGVfbWluIDwtIGlmZWxzZShzeW1tZXRyaWMsLW1heChhYnMoZGF0YSkpLG1pbihkYXRhKSkKICBzY2FsZV9taWQgPC0gc2NhbGVfbWluICsgKHNjYWxlX21heCAtIHNjYWxlX21pbikvMgogIAogIG1pZHBvaW50IDwtIGlmZWxzZShpcy5udWxsKG1pZHBvaW50KSwgc2NhbGVfbWlkLCBtaWRwb2ludCkKICAKICAjIFNldCBkZWZhdWx0IGNvbG9yc2NhbGVzIGJhc2VkIG9uIHN5bW1ldHJ5IG9mIGRhdGEKICBpZiAoaXMubnVsbChjb2xvcl9zY2FsZSkgJiBzeW1tZXRyaWMpe2NvbG9yX3NjYWxlIDwtIGhjbC5jb2xvcnMoMywgIkVhcnRoIil9CiAgaWYgKGlzLm51bGwoY29sb3Jfc2NhbGUpICYgIXN5bW1ldHJpYyl7Y29sb3Jfc2NhbGUgPC0gdmlyaWRpczo6dmlyaWRpcygzKX0KICBjb2xvcl9mdW5jdGlvbiA8LWNpcmNsaXplOjpjb2xvclJhbXAyKGMoc2NhbGVfbWluLG1pZHBvaW50LHNjYWxlX21heCksIGNvbG9yX3NjYWxlKQogIAogICMgTGVnZW5kIHBhcmFtZXRlcnMKICBsZWdlbmRfcGFyYW1zIDwtIGxpc3QoCiAgICAgICJ0aXRsZV9ncCIgPSBncmlkOjpncGFyKGZvbnRzaXplID0gbGVnZW5kX3RpdGxlX3NpemUsIGZvbnRmYWNlID0gImJvbGQiKSwKICAgICAgImxhYmVsc19ncCIgPSBncmlkOjpncGFyKGZvbnRzaXplID0gbGVnZW5kX2xhYmVsX3NpemUpLAogICAgICAiZGlyZWN0aW9uIiA9IGxlZ2VuZF9vcmllbnRhdGlvbgogICAgICApCiAgCiAgIyBIZWF0bWFwIHBhcmFtZXRlcnMKICBoZWF0bWFwX2FyZ3VtZW50cyA8LSBsaXN0KAogICAgIm1hdHJpeCIgPSBkYXRhLAogICAgImNvbCIgPSBjb2xvcl9mdW5jdGlvbiwKICAgICJyb3dfbmFtZXNfZ3AiID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IG1hdHJpeF9uYW1lc19zaXplKSwKICAgICJjb2x1bW5fbmFtZXNfZ3AiID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IG1hdHJpeF9uYW1lc19zaXplKSwKICAgICJzaG93X2NvbHVtbl9kZW5kIj1kZW5kcm9ncmFtcywKICAgICJzaG93X3Jvd19kZW5kIj1kZW5kcm9ncmFtcwogICkKICAKICBpZighaXMubnVsbChsZWdlbmRfdGl0bGUpKXtoZWF0bWFwX2FyZ3VtZW50c1tbIm5hbWUiXV0gPC0gbGVnZW5kX3RpdGxlfQogIGlmKGdyaWRfbGluZXMpe2hlYXRtYXBfYXJndW1lbnRzW1sicmVjdF9ncCJdXSA8LSBncmlkOjpncGFyKGNvbCA9ICJibGFjayIsIGx3ZCA9IDEpfQogIGlmKGRlbmRyb2dyYW1zKXsKICAgIGhlYXRtYXBfYXJndW1lbnRzW1snY29sdW1uX2RlbmRfaGVpZ2h0J11dIDwtIGRlbmRyb2dyYW1fd2VpZ2h0CiAgICBoZWF0bWFwX2FyZ3VtZW50c1tbJ3Jvd19kZW5kX3dpZHRoJ11dIDwtIGRlbmRyb2dyYW1fd2VpZ2h0CiAgfQogIAogIGxlZ2VuZF9zaWRlIDwtIGlmZWxzZShsZWdlbmRfb3JpZW50YXRpb249PSJ2ZXJ0aWNhbCIsInJpZ2h0IiwiYm90dG9tIikKICBpZihsZWdlbmRfb3JpZW50YXRpb249PSJ2ZXJ0aWNhbCIgJiFpcy5udWxsKGxlZ2VuZF9sZW5ndGgpKXsKICAgIGxlZ2VuZF9wYXJhbXNbWydsZWdlbmRfaGVpZ2h0J11dIDwtIGxlZ2VuZF9sZW5ndGh9CiAgaWYobGVnZW5kX29yaWVudGF0aW9uPT0iaG9yaXpvbnRhbCIgJiFpcy5udWxsKGxlZ2VuZF9sZW5ndGgpKXsKICAgIGxlZ2VuZF9wYXJhbXNbWydsZWdlbmRfd2lkdGgnXV0gPC0gbGVnZW5kX2xlbmd0aH0KICAjIEZ1bmN0aW9uIGNhbGwKICBoZWF0bWFwX2FyZ3VtZW50c1tbImhlYXRtYXBfbGVnZW5kX3BhcmFtIl1dIDwtIGxlZ2VuZF9wYXJhbXMKICBodCA8LSBkby5jYWxsKEhlYXRtYXAsIGhlYXRtYXBfYXJndW1lbnRzKQogIGRyYXcoaHQsIGhlYXRtYXBfbGVnZW5kX3NpZGU9bGVnZW5kX3NpZGUsIGFsaWduX2hlYXRtYXBfbGVnZW5kID0gImdsb2JhbF9jZW50ZXIiKQogIAp9CgpjdXN0b21faGVhdG1hcChhdmVyYWdlX2Nvc2luZV9tYXRyaXgsIHN5bW1ldHJpYyA9IEYsIGxlZ2VuZF90aXRsZSA9ICJDb3NpbmUgc2ltaWxhcml0eSIsIGdyaWRfbGluZXMgPSBULCBkZW5kcm9ncmFtcyA9IEYsIGxlZ2VuZF9vcmllbnRhdGlvbiA9ICJob3Jpem9udGFsIiwgbGVnZW5kX2xlbmd0aD11bml0KDEwLCAiY20iKSkKYGBgCmBgYHtyfQpkZl9jb21wIDwtIGxpc3ROKAogIGRmX2dwdDMuNV9pY2RfY29kaWFnLAogIGRmX2dwdDQuMF9pY2RfY29kaWFnLAogIGRmX2NsYXVkZTNfaGFpa3VfdDEuMF9pY2RfY29kaWFnLAogIGRmX2NsYXVkZTNfb3B1c190MS4wX2ljZF9jb2RpYWcsCiAgZGZfZ2VtaW5pMS4wX3Byb190MS4wX2ljZF9jb2RpYWcsCiAgZGZfZ2VtaW5pMS41X3Byb190MS4wX2ljZF9jb2RpYWcKKSAlPiUKICBlbmZyYW1lKG5hbWUgPSAibW9kZWwiLCB2YWx1ZSA9ICJkYXRhIikgJT4lCiAgbXV0YXRlKGRhdGEgPSBtYXAoZGF0YSwgY2VudHJhbGl0eV93cmFwcGVyKSkgJT4lCiAgdW5uZXN0KGRhdGEpCmBgYApgYGB7ciwgZmlnLndpZHRoPTMuNSwgZmlnLmhlaWdodD00fQpjb3NpbmVfc2ltaWxhcml0eV9jb21wYXJlIDwtIGZ1bmN0aW9uKGRmLCBwdF9zaXplPTEscF9zaXplPTMpewogICMgRm9ybWF0IGRhdGEKICBkZiA8LSBkZiAlPiUgCiAgICB1bml0ZShjb21wLCBWMSwgVjIpICU+JSAKICAgIGZpbHRlcihjb21wICVpbiUgYygiTUNBUyAtIENvbnNvcnRpdW1fTUNBUyAtIEFsdGVybmF0aXZlIiwgIlNMRSAtIEVVTEFSLUFDUl9TTEUgLSBTTElDQyIpKSAlPiUgCiAgICBtdXRhdGUoY29tcCA9IGdzdWIoIl8iLCAiXG52cy4gIiwgY29tcCkpICU+JSAgCiAgICBmb3JtYXRfbW9kZWxzKCkgCiAgCiAgIyBQbG90IGRhdGEKICBkZiAlPiUgCiAgICBnZ3Bsb3QoYWVzKHggPSBjb21wLCB5ID0gY29zaW5lLCBjb2xvciA9IG1vZGVsKSkrCiAgICBnZW9tX3BvaW50KHNpemU9cHRfc2l6ZSwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuNzUpKSsKICAgIHRoZW1lX2J3KCkgKwogICAgZ2dwdWJyOjpzdGF0X2NvbXBhcmVfbWVhbnMoYWVzKGdyb3VwID0gY29tcCksIG1ldGhvZCA9ICJ3aWxjb3gudGVzdCIsIGxhYmVsID0gInAiLCB2anVzdCA9IDAuNzUsIHNob3cubGVnZW5kID0gRiwgc2l6ZSA9IHBfc2l6ZSkrCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9OTAsIGhqdXN0ID0xKSkgKwogICAgbGFicyh4PU5VTEwsIHk9IkNvc2luZSBzaW1pbGFyaXR5IikgKwogICAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiRGFyazIiKSArCiAgICBsYWJzKGNvbG9yID0gIiIpCn0KY29zaW5lX3NpbWlsYXJpdHlfY29tcGFyZShkZl9jb21wKQpgYGAKCmBgYHtyfQppbmRpdmlkdWFsX2Nvc2luZV9tYXRyaXggPC0gY29tYmluZV9kYXRhX2ZyYW1lcygKICBkZl9ncHQzLjVfaWNkX2NvZGlhZywKICBkZl9ncHQ0LjBfaWNkX2NvZGlhZywKICBkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfaWNkX2NvZGlhZywKICBkZl9jbGF1ZGUzX29wdXNfdDEuMF9pY2RfY29kaWFnLAogIGRmX2dlbWluaTEuMF9wcm9fdDEuMF9pY2RfY29kaWFnLAogIGRmX2dlbWluaTEuNV9wcm9fdDEuMF9pY2RfY29kaWFnLAogIGFkZGl0aW9uYWxfZnVuY3Rpb24gPSBmdW5jdGlvbih4KXsKICAgIHggJT4lIAogICAgICBtYWtlX2NvZGlhZ25vc2lzX2dyYXBoKCkgJT4lIAogICAgICBjYWxjdWxhdGVfc3ViZ3JhcGhfY2VudHJhbGl0eSgpICU+JSAKICAgICAgcm93bmFtZXNfdG9fY29sdW1uKCJjcml0ZXJpYSIpICU+JSAKICAgICAgcGl2b3RfbG9uZ2VyKC1jcml0ZXJpYSwgbmFtZXNfdG89ImRpYWdub3NpcyIsIHZhbHVlc190bz0iY2VudHJhbGl0eSIpCiAgfQopICU+JSAKICB1bml0ZShjcml0ZXJpYSwgb3JpZ2luYWxfZGYsIGNyaXRlcmlhLCBzZXAgPSAiLSIpICU+JSAKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gImRpYWdub3NpcyIsIHZhbHVlc19mcm9tPSJjZW50cmFsaXR5IiwgdmFsdWVzX2ZpbGw9MCkgJT4lIAogIGNvbHVtbl90b19yb3duYW1lcygiY3JpdGVyaWEiKSAlPiUgCiAgYXMubWF0cml4KCkgJT4lIAogICAgdCgpICU+JSAKICAgIGxzYTo6Y29zaW5lKCkgJT4lIAogICAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICAgIHJvd25hbWVzX3RvX2NvbHVtbigiVjEiKSAlPiUgCiAgICBwaXZvdF9sb25nZXIoLVYxLCBuYW1lc190byA9ICJWMiIsIHZhbHVlc190byA9ICJjb3NpbmUiKSAlPiUgCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9ICJWMiIsIHZhbHVlc19mcm9tID0gImNvc2luZSIpICU+JQogIGNvbHVtbl90b19yb3duYW1lcygiVjEiKSAlPiUKICBhcy5tYXRyaXgoKQoKaW5kaXZpZHVhbF9jb3NpbmVfbWF0cml4WzE6NSwgMTo1XQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9NS41LCBmaWcuaGVpZ2h0PTMuNX0KYWx0X2hlYXRtYXAgPC0gbW9kZWxfY3JpdGVyaWFfaGVhdG1hcChpbmRpdmlkdWFsX2Nvc2luZV9tYXRyaXgsIAogICAgICAgICAgICAgICAgY29sb3Jfc2NhbGUgPSB2aXJpZGlzOjp2aXJpZGlzKDMpLCAKICAgICAgICAgICAgICAgIHRpdGxlID0gIiAiLCAKICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJDb3NpbmVcbnNpbWlsYXJpdHkiLCAKICAgICAgICAgICAgICAgIHN5bW1ldHJpYyA9IEYsCiAgICAgICAgICAgICAgICBmb250X3NpemUgPSA4KSAgCiAgCgphbHRfaGVhdG1hcApgYGAKYGBge3IsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTR9CnBsdF9hbHQgPC0gY293cGxvdDo6cGxvdF9ncmlkKGdyaWQ6OmdyaWQuZ3JhYkV4cHIoQ29tcGxleEhlYXRtYXA6OmRyYXcoYWx0X2hlYXRtYXApKSkKcGx0X2FsdApgYGAKYGBge3J9Cmdnc2F2ZShoZXJlKCJmaWd1cmVzLzRfYWx0X2hlYXRtYXAucGRmIiksIHBsb3Q9cGx0X2FsdCwgaGVpZ2h0ID0gNCwgd2lkdGggPSA2KQpgYGAKCiMgRmluYWwgcGxvdAoKIyMjIFZlcnNpb24gMQpgYGB7ciwgZmlnLndpZHRoPTMuNSwgZmlnLmhlaWdodD01LjV9CiMgbl9kaWFnbm9zZXNfYmFyIDwtIDEwCiMgbl9kaWFnbm9zZXNfYWJ1bmRhbmNlIDwtIDUwCiMgbl9kaWFnbm9zZXNfY3VtdWxhdGl2ZSA8LSA1MAoKdGl0bGVfc2l6ZSA8LSA5CmxhYmVsX3NpemUgPC0gNgpsZWdlbmRfeF9wYWQgPC0gMApsZWdlbmRfeV9wYWQgPC0gMAoKYXBwbHlfdGV4dF9mb3JtYXR0aW5nIDwtIGxpc3QoCiAgdGhlbWUoCiAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IGxhYmVsX3NpemUpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gdGl0bGVfc2l6ZSksCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gbGFiZWxfc2l6ZSwgbWFyZ2luPW1hcmdpbigwLDAsMCxyPTEpKSwKICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IGxhYmVsX3NpemUgKyAxKSwKICAgIGxlZ2VuZC5rZXkuaGVpZ2h0ID0gdW5pdCgwLjQsICdjbScpLAogICAgbGVnZW5kLmtleS53aWR0aCA9IHVuaXQoMC40LCAnY20nKSwKICAgICMgbGVnZW5kLmtleSA9IGVsZW1lbnRfcmVjdChzaXplID0gIG1hcmdpbigwLDAsMCwwKSksCiAgICBsZWdlbmQuYm94LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiYmxhY2siLCBzaXplID0gMSksCiAgICBsZWdlbmQubWFyZ2luID0gbWFyZ2luKAogICAgICB0ID0gbGVnZW5kX3lfcGFkLAogICAgICByID0gbGVnZW5kX3hfcGFkKzIsCiAgICAgIGIgPSBsZWdlbmRfeV9wYWQsCiAgICAgIGwgPSBsZWdlbmRfeF9wYWQgCiAgICApLAogICAgbGVnZW5kLmtleS5zcGFjaW5nLnkgPSB1bml0KC0xLjUsICJwdCIpLAogICAgbGVnZW5kLmJveC5zcGFjaW5nID0gdW5pdCgxLjAsInB0IiksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9NDUsaGp1c3Q9MSkKICApCikKCgpzdHJpcF9tYXJnaW4gPC0gMQpzdHJpcF9mb3JtYXR0aW5nIDwtIGxpc3QodGhlbWUoCiAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IG1hcmdpbih0ID0gc3RyaXBfbWFyZ2luLCByID0gc3RyaXBfbWFyZ2luLCBiID0gc3RyaXBfbWFyZ2luLCBsID0gc3RyaXBfbWFyZ2luKSksIAogIHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChtYXJnaW4gPSBtYXJnaW4odCA9IHN0cmlwX21hcmdpbiwgciA9IHN0cmlwX21hcmdpbiwgYiA9IHN0cmlwX21hcmdpbiwgbCA9IHN0cmlwX21hcmdpbikpCiAgIyBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KG1hcmdpbiA9IG1hcmdpbih0ID0gc3RyaXBfbWFyZ2luLCByID0gc3RyaXBfbWFyZ2luLCBiID0gc3RyaXBfbWFyZ2luLCBsID0gc3RyaXBfbWFyZ2luKSkKKSkKCnNldC5zZWVkKDEyMzQpCnBsdF9ncmFwaF9pY2QgPC0KICBtdWx0aV9tYWtlX2NvZGlhZ25vc2lzX2dyYXBoKAogICAgdGhyZXNob2xkX21ldGhvZCA9ICJhdmVyYWdlIiwKICAgIHRvcF9uID0gMTAwLAogICAgbGF5b3V0ID0gInN0cmVzcyIsCiAgICBkZl9ncHQzLjVfY29kaWFnLAogICAgZGZfZ3B0NC4wX2NvZGlhZywKICAgIGRmX2NsYXVkZTNfaGFpa3VfdDEuMF9jb2RpYWcsCiAgICBkZl9jbGF1ZGUzX29wdXNfdDEuMF9jb2RpYWcsCiAgICBkZl9nZW1pbmkxLjBfcHJvX3QxLjBfY29kaWFnLAogICAgZGZfZ2VtaW5pMS41X3Byb190MS4wX2NvZGlhZywKICAgIHBvaW50X3NpemUgPSAxLjI1LAogICAgYm9yZGVyX3NpemUgPSAwLjI1LAogICAgZWRnZV93aWR0aCA9IDAuNSwKICAgIGVkZ2VfYWxwaGEgPSAwLjUsCiAgICBsYWJlbF90ZXh0X3NpemUgPSA5LAogICAgdGlja190ZXh0X3NpemUgPSA2LAogICAgaGlnaGxpZ2h0X3N0cm9rZV9tdWx0aXBsaWVyID0gMywKICAgIGxlZ2VuZF9oZWlnaHQgPSB1bml0KDI1LCAicHQiKSwKICAgIGxlZ2VuZF93aWR0aCA9IHVuaXQoMTAsICJwdCIpCiAgKSAKCnBsdF9lZGdlX2ljZCA8LSBtdWx0aV9lZGdlX2RlbnNpdHlfcGxvdCgKICBkZl9ncHQzLjVfaWNkX2NvZGlhZywKICBkZl9ncHQ0LjBfaWNkX2NvZGlhZywKICBkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfaWNkX2NvZGlhZywKICBkZl9jbGF1ZGUzX29wdXNfdDEuMF9pY2RfY29kaWFnLAogIGRmX2dlbWluaTEuMF9wcm9fdDEuMF9pY2RfY29kaWFnLAogIGRmX2dlbWluaTEuNV9wcm9fdDEuMF9pY2RfY29kaWFnCikgICsKICBhcHBseV90ZXh0X2Zvcm1hdHRpbmcgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCBsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDEpLCBuY29sID0gMikpKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMC4xLDAuMSkpKQoKcGx0X2hlYXRtYXBfaWNkIDwtCiAgY3VzdG9tX2hlYXRtYXAoCiAgICBhdmVyYWdlX2Nvc2luZV9tYXRyaXgsCiAgICBzeW1tZXRyaWMgPSBGLAogICAgbGVnZW5kX3RpdGxlID0gIkNvc2luZSBzaW1pbGFyaXR5IiwKICAgIGdyaWRfbGluZXMgPSBULAogICAgZGVuZHJvZ3JhbXMgPSBULAogICAgbGVnZW5kX29yaWVudGF0aW9uID0gImhvcml6b250YWwiLAogICAgbGVnZW5kX2xlbmd0aCA9IHVuaXQoMiwgImNtIiksCiAgICBtYXRyaXhfbmFtZXNfc2l6ZSA9IDYsIAogICAgbGVnZW5kX3RpdGxlX3NpemUgPSA3LjUsCiAgICBsZWdlbmRfbGFiZWxfc2l6ZSA9IDYsCiAgICBkZW5kcm9ncmFtX3dlaWdodCA9IHVuaXQoMiwgIm1tIikKICApCgoKcGx0X2ZpZyA8LSBjb3dwbG90OjpwbG90X2dyaWQoCiAgTlVMTCwgCiAgY293cGxvdDo6cGxvdF9ncmlkKAogICAgTlVMTCwgcGx0X2dyYXBoX2ljZCwgbnJvdz0xLCByZWxfd2lkdGhzPWMoMC4wNSwwLjkpCiAgKSwKICBOVUxMLAogIGNvd3Bsb3Q6OnBsb3RfZ3JpZCgKICAgIGNvd3Bsb3Q6OnBsb3RfZ3JpZChOVUxMLCBwbHRfZWRnZV9pY2QgLHJlbF9oZWlnaHRzID0gYygwLjEsIDEpLCBuY29sID0gMSksCiAgICBOVUxMLAogICAgY293cGxvdDo6cGxvdF9ncmlkKAogICAgICBOVUxMLAogICAgICBncmlkOjpncmlkLmdyYWJFeHByKENvbXBsZXhIZWF0bWFwOjpkcmF3KHBsdF9oZWF0bWFwX2ljZCwgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJib3R0b20iKSksCiAgICAgIE5VTEwsCiAgICAgIG5jb2wgPSAxLAogICAgICByZWxfaGVpZ2h0cyA9IGMoMC4xLCAxLCAwLjEpCiAgICApLAogICAgbnJvdyA9IDEsCiAgICByZWxfd2lkdGhzID0gYygwLjUsIDAuMSwgMC42KSwKICAgIGxhYmVscyA9IGMoIkIiLCIiLCJDIiksCiAgICBheGlzID0gJ2gnLCBhbGlnbiA9ICdidCcKICApLAoKICBuY29sID0gMSwKICByZWxfaGVpZ2h0cyA9IGMoMC4wNSwgMC45NSwgMC4wMSwgMSksCiAgbGFiZWxzID0gYygiQSIpCikKCnBsdF9maWcgCmBgYAoKIyMjIFZlcnNpb24gMgpgYGB7ciwgZmlnLndpZHRoPTMuNSwgZmlnLmhlaWdodD01LjV9CiMgbl9kaWFnbm9zZXNfYmFyIDwtIDEwCiMgbl9kaWFnbm9zZXNfYWJ1bmRhbmNlIDwtIDUwCiMgbl9kaWFnbm9zZXNfY3VtdWxhdGl2ZSA8LSA1MAoKdGl0bGVfc2l6ZSA8LSA5CmxhYmVsX3NpemUgPC0gNgpsZWdlbmRfeF9wYWQgPC0gMApsZWdlbmRfeV9wYWQgPC0gMAoKYXBwbHlfdGV4dF9mb3JtYXR0aW5nIDwtIGxpc3QoCiAgdGhlbWUoCiAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IGxhYmVsX3NpemUpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gdGl0bGVfc2l6ZSksCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gbGFiZWxfc2l6ZSwgbWFyZ2luPW1hcmdpbigwLDAsMCxyPTEpKSwKICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IGxhYmVsX3NpemUgKyAxKSwKICAgIGxlZ2VuZC5rZXkuaGVpZ2h0ID0gdW5pdCgwLjQsICdjbScpLAogICAgbGVnZW5kLmtleS53aWR0aCA9IHVuaXQoMC40LCAnY20nKSwKICAgICMgbGVnZW5kLmtleSA9IGVsZW1lbnRfcmVjdChzaXplID0gIG1hcmdpbigwLDAsMCwwKSksCiAgICBsZWdlbmQuYm94LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiYmxhY2siLCBzaXplID0gMSksCiAgICBsZWdlbmQubWFyZ2luID0gbWFyZ2luKAogICAgICB0ID0gbGVnZW5kX3lfcGFkLAogICAgICByID0gbGVnZW5kX3hfcGFkKzIsCiAgICAgIGIgPSBsZWdlbmRfeV9wYWQsCiAgICAgIGwgPSBsZWdlbmRfeF9wYWQgCiAgICApLAogICAgbGVnZW5kLmtleS5zcGFjaW5nLnkgPSB1bml0KC0xLjUsICJwdCIpLAogICAgbGVnZW5kLmJveC5zcGFjaW5nID0gdW5pdCgxLjAsInB0IiksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9OTAsIHZqdXN0PTAuNSkKICApCikKCgpzdHJpcF9tYXJnaW4gPC0gMQpzdHJpcF9mb3JtYXR0aW5nIDwtIGxpc3QodGhlbWUoCiAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IG1hcmdpbih0ID0gc3RyaXBfbWFyZ2luLCByID0gc3RyaXBfbWFyZ2luLCBiID0gc3RyaXBfbWFyZ2luLCBsID0gc3RyaXBfbWFyZ2luKSksIAogIHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChtYXJnaW4gPSBtYXJnaW4odCA9IHN0cmlwX21hcmdpbiwgciA9IHN0cmlwX21hcmdpbiwgYiA9IHN0cmlwX21hcmdpbiwgbCA9IHN0cmlwX21hcmdpbikpCiAgIyBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KG1hcmdpbiA9IG1hcmdpbih0ID0gc3RyaXBfbWFyZ2luLCByID0gc3RyaXBfbWFyZ2luLCBiID0gc3RyaXBfbWFyZ2luLCBsID0gc3RyaXBfbWFyZ2luKSkKKSkKCnNldC5zZWVkKDEyMzQpCnBsdF9ncmFwaF9pY2QgPC0KICBtdWx0aV9tYWtlX2NvZGlhZ25vc2lzX2dyYXBoKAogICAgdGhyZXNob2xkX21ldGhvZCA9ICJhdmVyYWdlIiwKICAgIHRvcF9uID0gMTAwLAogICAgbGF5b3V0ID0gInN0cmVzcyIsCiAgICBkZl9ncHQzLjVfY29kaWFnLAogICAgZGZfZ3B0NC4wX2NvZGlhZywKICAgIGRmX2NsYXVkZTNfaGFpa3VfdDEuMF9jb2RpYWcsCiAgICBkZl9jbGF1ZGUzX29wdXNfdDEuMF9jb2RpYWcsCiAgICBkZl9nZW1pbmkxLjBfcHJvX3QxLjBfY29kaWFnLAogICAgZGZfZ2VtaW5pMS41X3Byb190MS4wX2NvZGlhZywKICAgIHBvaW50X3NpemUgPSAxLjI1LAogICAgYm9yZGVyX3NpemUgPSAwLjI1LAogICAgZWRnZV93aWR0aCA9IDAuNSwKICAgIGVkZ2VfYWxwaGEgPSAwLjUsCiAgICBsYWJlbF90ZXh0X3NpemUgPSA5LAogICAgdGlja190ZXh0X3NpemUgPSA2LAogICAgaGlnaGxpZ2h0X3N0cm9rZV9tdWx0aXBsaWVyID0gMywKICAgIGxlZ2VuZF9oZWlnaHQgPSB1bml0KDI1LCAicHQiKSwKICAgIGxlZ2VuZF93aWR0aCA9IHVuaXQoMTAsICJwdCIpCiAgKSAKCnBsdF9lZGdlX2ljZCA8LSBtdWx0aV9lZGdlX2RlbnNpdHlfcGxvdCgKICBkZl9ncHQzLjVfaWNkX2NvZGlhZywKICBkZl9ncHQ0LjBfaWNkX2NvZGlhZywKICBkZl9jbGF1ZGUzX2hhaWt1X3QxLjBfaWNkX2NvZGlhZywKICBkZl9jbGF1ZGUzX29wdXNfdDEuMF9pY2RfY29kaWFnLAogIGRmX2dlbWluaTEuMF9wcm9fdDEuMF9pY2RfY29kaWFnLAogIGRmX2dlbWluaTEuNV9wcm9fdDEuMF9pY2RfY29kaWFnCikgICsKICBhcHBseV90ZXh0X2Zvcm1hdHRpbmcgKwogICMgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsIGxlZ2VuZC5kaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIpICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMSksIG5jb2wgPSAyKSkrCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLjEsMC4xKSkpCgpwbHRfY29zaW5lIDwtIGNvc2luZV9zaW1pbGFyaXR5X2NvbXBhcmUoZGZfY29tcCwgcF9zaXplID0gMikgKwogIGFwcGx5X3RleHRfZm9ybWF0dGluZwoKcGx0X2hlYXRtYXBfaWNkIDwtCiAgY3VzdG9tX2hlYXRtYXAoCiAgICBhdmVyYWdlX2Nvc2luZV9tYXRyaXgsCiAgICBzeW1tZXRyaWMgPSBGLAogICAgbGVnZW5kX3RpdGxlID0gIkNvc2luZSBzaW1pbGFyaXR5IiwKICAgIGdyaWRfbGluZXMgPSBULAogICAgZGVuZHJvZ3JhbXMgPSBULAogICAgbGVnZW5kX29yaWVudGF0aW9uID0gImhvcml6b250YWwiLAogICAgbGVnZW5kX2xlbmd0aCA9IHVuaXQoMi41LCAiY20iKSwKICAgIG1hdHJpeF9uYW1lc19zaXplID0gNiwgCiAgICBsZWdlbmRfdGl0bGVfc2l6ZSA9IDcuNSwKICAgIGxlZ2VuZF9sYWJlbF9zaXplID0gNiwKICAgIGRlbmRyb2dyYW1fd2VpZ2h0ID0gdW5pdCgyLjUsICJtbSIpCiAgKQoKcGQgPC0gLTEKCnBsdF9maWcgPC0gY293cGxvdDo6cGxvdF9ncmlkKAogICMxCiAgTlVMTCwgCiAgIzIKICBjb3dwbG90OjpwbG90X2dyaWQoCiAgICBOVUxMLCBwbHRfZ3JhcGhfaWNkLCBucm93PTEsIHJlbF93aWR0aHM9YygwLjA1LDAuOSkKICApLAogICMzCiAgTlVMTCwKICAjNAogIGNvd3Bsb3Q6OnBsb3RfZ3JpZCgKICAgICM0LjEKICAgIGNvd3Bsb3Q6OnBsb3RfZ3JpZCgKICAgICAgIzQuMS4xCiAgICAgIHBsb3RfZ3JpZCgKICAgICAgICAjNC4xLjEuMQogICAgICAgIHBsdF9lZGdlX2ljZCt0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpLAogICAgICAgICM0LjEuMS4yCiAgICAgICAgcGx0X2Nvc2luZSt0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSt5bGltKDAuNCwxKSwgCiAgICAgICAgIyByZWxfd2lkdGhzID0gYygxMCw5KSwKICAgICAgICBucm93PTEsCiAgICAgICAgYWxpZ249ImgiLGF4aXM9ImJ0IgogICAgICAgICksCiAgICAgICM0LjEuMgogICAgICBOQSwKICAgICAgcGxvdF9ncmlkKE5VTEwsZ2V0X2xlZ2VuZChwbHRfZWRnZV9pY2QpLG5yb3c9MSxyZWxfd2lkdGhzPWMoMC4xNSwxKSksCiAgICAgIE5BLAogICAgICBuY29sPTEsCiAgICAgIHJlbF9oZWlnaHRzID0gYygxLCAwLjA0LCAwLjEsMC4xKSAjNC4xLl8KICAgICksCiAgICAjNC4yCiAgICBjb3dwbG90OjpwbG90X2dyaWQoCiAgICAgICM0LjIuMQogICAgICBOVUxMLAogICAgICAjNC4yLjIKICAgICAgZ3JpZDo6Z3JpZC5ncmFiRXhwcihDb21wbGV4SGVhdG1hcDo6ZHJhdyhwbHRfaGVhdG1hcF9pY2QsIGhlYXRtYXBfbGVnZW5kX3NpZGUgPSAiYm90dG9tIiwgcGFkZGluZyA9IHVuaXQoYygwLDAsMCwtMSksICJtbSIpKSksCiAgICAgICM0LjIuMwogICAgICBOVUxMLAogICAgICBuY29sID0gMSwKICAgICAgcmVsX2hlaWdodHMgPSBjKDAuMSwgMSwgMC4xKQogICAgKSwKICAgIG5yb3cgPSAxLAogICAgcmVsX3dpZHRocyA9IGMoMC42LCAgMC41KSwgIzQuXwogICAgYWxpZ24gPSAnaCcsIGF4aXMgPSAnYnQnCiAgKSwKCiAgbmNvbCA9IDEsCiAgcmVsX2hlaWdodHMgPSBjKDAuMDUsIDAuOTUsIDAuMDUsIDEpCikKCnBsdF9maWcgPC0gY293cGxvdDo6Z2dkcmF3KHBsdF9maWcpK2Nvd3Bsb3Q6OmRyYXdfcGxvdF9sYWJlbChjKCJBIiwiQiIsIkMiLCJEIiksIHg9YygwLDAsMC4yNSwwLjUyKSwgeT1jKDEscmVwKDAuNTIsMykpKQpwbHRfZmlnIApgYGAKCgpgYGB7ciwgZXZhbD1GfQpnZ3NhdmUoaGVyZSgiZmlndXJlcy80X05ldHdvcmtfYW5hbHlzaXMucGRmIiksIHBsb3Q9cGx0X2ZpZywgaGVpZ2h0ID0gNS41LCB3aWR0aCA9IDMuNSkKYGBgCgo=